/**
 * 
 */
package com.skivingcloud.workflow.service.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.StartEvent;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.PvmActivity;
import org.activiti.engine.impl.pvm.PvmTransition;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ModelQuery;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.image.PNGTranscoder;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.skivingcloud.admin.sys.entity.User;
import com.skivingcloud.common.exception.BusinessException;
import com.skivingcloud.common.utils.PageUtils;
import com.skivingcloud.common.utils.StringUtils;
import com.skivingcloud.common.utils.UuidUtil;
import com.skivingcloud.workflow.entity.DepolymentInfo;
import com.skivingcloud.workflow.entity.ProcessVO;
import com.skivingcloud.workflow.service.IDepolymentInfoService;
import com.skivingcloud.workflow.service.ModelService;

import net.sf.json.JSONObject;

/**
 * @author hushouquan
 *
 */
@Service
public class ModelServiceImpl implements ModelService {

	@Autowired
    private RepositoryService repositoryService;
	@Autowired
    private ObjectMapper objectMapper;
	@Autowired
    private IDepolymentInfoService depolymentInfoService;
	/**
	 * 分页查询模型
	 * @param params
	 * @param user
	 * @return
	 */
	@Override
	public PageUtils list(Map<String, Object> params, User user) {
		String searchName = params.get("searchName") == null ? "" : params.get("searchName").toString();
        int pagesize = Integer.parseInt(params.get("page").toString());
        int limit = Integer.parseInt(params.get("limit").toString());
        int offset = (pagesize - 1) * limit;
        ModelQuery query = repositoryService.createModelQuery();
        if(StringUtils.isNotBlank(searchName)){
            query =  query.modelNameLike("%" + searchName + "%");
        }
        query.orderByLastUpdateTime().desc();
        List<Model> list = query.latestVersion().modelTenantId(user.getTenantId())
                .orderByCreateTime().desc().listPage(offset, limit);
        int total = (int) query.latestVersion().count();
        PageUtils pageUtil = new PageUtils(list, total);
		return pageUtil;
	}

	/**
	 * 初始化一个空的模型
	 * @return
	 */
	@Override
	public String newModel(User user) {
		// 初始化一个空模型
        Model model = repositoryService.newModel();
        // 设置一些默认信息
        String name = "new-process";
        String description = "";
        int revision = 1;
        String key = "process";
        ObjectNode modelNode = objectMapper.createObjectNode();
        modelNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelNode.put(ModelDataJsonConstants.MODEL_REVISION, revision);
        model.setName(name);
        model.setKey(key);
        model.setMetaInfo(modelNode.toString());
        model.setTenantId(user.getTenantId());
        repositoryService.saveModel(model);
        String id = model.getId();
        // 完善ModelEditorSource
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");
        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.set("stencilset", stencilSetNode);
        repositoryService.addModelEditorSource(id, editorNode.toString().getBytes(StandardCharsets.UTF_8));
		return id;
	}

	/**
	 * 根据id加载模型数据
	 * @param modelId
	 * @return
	 * @throws Exception 
	 */
	@Override
	public ObjectNode getEditorJson(String modelId) throws Exception {
		Model model = repositoryService.getModel(modelId);
		ObjectNode modelNode = null;
		if (model != null) {
            try {
                if (org.apache.commons.lang3.StringUtils.isNotEmpty(model.getMetaInfo())) {
                    modelNode = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
                } else {
                    modelNode = objectMapper.createObjectNode();
                    modelNode.put(ModelDataJsonConstants.MODEL_NAME, model.getName());
                }
                modelNode.put(ModelDataJsonConstants.MODEL_ID, model.getId());
                ObjectNode editorJsonNode = (ObjectNode) objectMapper
                        .readTree(new String(repositoryService.getModelEditorSource(model.getId()), StandardCharsets.UTF_8));
                modelNode.set("model", editorJsonNode);

            } catch (Exception e) {
               throw e;
            }
        }
		return modelNode;
	}

	/**
	 * 删除一个空模型
	 * @param modelId
	 */
	@Override
    public void deleteNoneModel(String modelId) {
		Model model = repositoryService.getModel(modelId);
		if (model != null) {
		    if (repositoryService.getModelEditorSourceExtra(model.getId()) == null) {
		        repositoryService.deleteModel(model.getId());
		    }
		}
	}

	/**
	 * 根据id删除模型
	 * @param id
	 */
	@Override
	public void deleteModel(String id) {
		repositoryService.deleteModel(id);
	}

	/**
	 * 获取最新版的流程定义
	 * @return
	 */
	@Override
	public List<ProcessDefinition> getProcessOption() {
		return repositoryService.createProcessDefinitionQuery().latestVersion().list();
	}

	@Override
	public void saveModel(String modelId, String name, String description, String json_xml, String svg_xml) throws Exception {
		Model model = repositoryService.getModel(modelId);
        ObjectNode modelJson = (ObjectNode) objectMapper.readTree(model.getMetaInfo());
        modelJson.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelJson.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        model.setMetaInfo(modelJson.toString());
        model.setName(name);
        JSONObject object = JSONObject.fromObject(json_xml);
        JSONObject prop = JSONObject.fromObject(object.get("properties"));
        if(prop.containsKey("process_id") && StringUtils.isNotBlank(prop.get("process_id"))){
            model.setKey(prop.getString("process_id"));
        }else{
            throw new BusinessException("请填写流程编码");
        }
        repositoryService.saveModel(model);
        repositoryService.addModelEditorSource(model.getId(), json_xml.getBytes(StandardCharsets.UTF_8));
        InputStream svgStream = new ByteArrayInputStream(svg_xml.getBytes(StandardCharsets.UTF_8));
        TranscoderInput input = new TranscoderInput(svgStream);
        PNGTranscoder transcoder = new PNGTranscoder();
        // Setup output
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        TranscoderOutput output = new TranscoderOutput(outStream);

        // Do the transformation
        transcoder.transcode(input, output);
        final byte[] result = outStream.toByteArray();
        repositoryService.addModelEditorSourceExtra(model.getId(), result);
        outStream.close();
	}

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deploy(String id, User user) throws Exception{
		 // 获取模型
        Model modelData = repositoryService.getModel(id);
        byte[] bytes = repositoryService.getModelEditorSource(modelData.getId());
        Assert.notNull(bytes, "模型数据为空，请先设计流程并成功保存，再进行发布。");

        try {
            JsonNode modelNode = new ObjectMapper().readTree(bytes);

            BpmnModel model = new BpmnJsonConverter().convertToBpmnModel(modelNode);

            String processName = modelData.getName() + ".bpmn20.xml";
//            modelData.setTenantId(tenantId);
            Assert.isTrue(model.getProcesses().size() > 0, "数据模型不符要求，请至少设计一条主线流程。");
            byte[] bpmnBytes = new BpmnXMLConverter().convertToXML(model);


            /**
             * 发布流程
             */
            Deployment deployment = repositoryService.createDeployment().name(modelData.getName()).tenantId(modelData.getTenantId())
                    .addString(processName, new String(bpmnBytes, StandardCharsets.UTF_8)).deploy();

            modelData.setDeploymentId(deployment.getId());
            repositoryService.saveModel(modelData); //更新模型信息表
            /**
             * 保存发布相关信息
             */
            DepolymentInfo wfDeploymentInfo = new DepolymentInfo();
            wfDeploymentInfo.setDeploymentId(deployment.getId());
            wfDeploymentInfo.setId(UuidUtil.get32UUID());
            wfDeploymentInfo.setCreateTime(LocalDateTime.now());
            wfDeploymentInfo.setUserId(user.getId());
            wfDeploymentInfo.setModelId(modelData.getId());
            depolymentInfoService.save(wfDeploymentInfo);
        } catch (Exception ex) {
            throw new RuntimeException("流程发布失败！", ex);
        }
	}

	/**
	 * 加载流程文件内容
	 * @param deploymentId
	 * @param resType 
	 * @param processDefinition 
	 * @return
	 */
	@Override
	public InputStream resourceRead(String deploymentId, String resType, ProcessDefinition processDefinition) {
		String resourceName = "";
        if (resType.equals("image")) {
            resourceName = processDefinition.getDiagramResourceName();
        } else if (resType.equals("xml")) {
            resourceName = processDefinition.getResourceName();
        }

        InputStream resourceAsStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(), resourceName);
        return resourceAsStream;
	}

	/**
	 * 流程导入
	 * @param files
	 * @param user
	 */
	@Override
	public void improtDeploy(List<MultipartFile> files, User user) throws Exception{
		for (MultipartFile file : files) {
	        String fileName = file.getOriginalFilename().split("@")[1];
	
	        InputStream fileInputStream = file.getInputStream();
	        Deployment deployment = null;
	        String extension = FilenameUtils.getExtension(fileName);
	        String tenantId = file.getOriginalFilename().split("@")[0];
	        if (extension.equals("zip") || extension.equals("bar")) {
	            ZipInputStream zip = new ZipInputStream(fileInputStream);
	            deployment = repositoryService.createDeployment().addZipInputStream(zip).tenantId(tenantId).deploy();
	        } else if (extension.equals("png")) {
	            deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).tenantId(tenantId).deploy();
	        } else if (fileName.indexOf("bpmn20.xml") != -1) {
	            deployment = repositoryService.createDeployment().addInputStream(fileName, fileInputStream).tenantId(tenantId).deploy();
	        } else if (extension.equals("bpmn")) { // bpmn扩展名特殊处理，转换为bpmn20.xml
	            String baseName = FilenameUtils.getBaseName(fileName);
	            deployment = repositoryService.createDeployment().addInputStream(baseName + ".bpmn20.xml", fileInputStream).tenantId(tenantId).deploy();
	        } else {
	            throw new BusinessException("不支持的文件类型：" + extension);
	        }
	        String procDefId = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult().getId();
	        //将部署信息转换为模型
	        org.activiti.engine.repository.Model modelData = convertToModel(procDefId);
	        /**
	         * 保存发布相关信息
	         */
	        DepolymentInfo wfDeploymentInfo = new DepolymentInfo();
	        wfDeploymentInfo.setDeploymentId(deployment.getId());
	        wfDeploymentInfo.setId(UuidUtil.get32UUID());
	        wfDeploymentInfo.setCreateTime(LocalDateTime.now());
	        wfDeploymentInfo.setUserId(user.getId());
	        wfDeploymentInfo.setModelId(modelData.getId());
	        depolymentInfoService.save(wfDeploymentInfo);
		}
	}

	/**
	 * 将流程转化为模型
	 * @param procDefId
	 * @return
	 * @throws Exception
	 */
	@Override
	public Model convertToModel(String procDefId) throws Exception {
		ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId).singleResult();
        InputStream bpmnStream = repositoryService.getResourceAsStream(processDefinition.getDeploymentId(),
                processDefinition.getResourceName());
        XMLInputFactory xif = XMLInputFactory.newInstance();
        InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
        XMLStreamReader xtr = xif.createXMLStreamReader(in);
        BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

        BpmnJsonConverter converter = new BpmnJsonConverter();
        ObjectNode modelNode = converter.convertToJson(bpmnModel);
        org.activiti.engine.repository.Model modelData = repositoryService.newModel();
        modelData.setKey(processDefinition.getKey());
        modelData.setName(processDefinition.getName());
        modelData.setCategory(processDefinition.getCategory());//.getDeploymentId());
        modelData.setDeploymentId(processDefinition.getDeploymentId());
        modelData.setVersion(Integer.parseInt(String.valueOf(repositoryService.createModelQuery().modelKey(modelData.getKey()).count() + 1)));

        ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelData.getVersion());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());
        modelData.setMetaInfo(modelObjectNode.toString());
        modelData.setTenantId(processDefinition.getTenantId());

        repositoryService.saveModel(modelData);

        repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));

        return modelData;
	}

	/**
	 * 获取开始节点信息用于组织页面表单
	 * @param defKey
	 * @return
	 */
	@Override
	public StartEvent getModelStartNodeInfo(String defKey) {
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionKey(defKey).latestVersion().latestVersion().list();
        if(list.isEmpty()) {
        	return null;
        }
        BpmnModel model = repositoryService.getBpmnModel(list.get(0).getId());
        //获取流程所有节点信息
        // 获取所有节点
        Collection<FlowElement> flowElements = model.getMainProcess().getFlowElements();
        for (FlowElement e : flowElements) {
            if (e instanceof StartEvent) {
                StartEvent startEvent = (StartEvent) e;
                return startEvent;
            }
        }
        return null;
	}

	/**
     * 查询流程名称列表
	 * @param tenantId 
     *
     * @return
     */
	@Override
	public List<ProcessVO> getProcessNameList(String tenantId) {
		List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(tenantId).latestVersion().list();
        List<ProcessVO> processVOList = new ArrayList<>();
        list.forEach(item -> {
            ProcessVO processVO = new ProcessVO(item);
            processVOList.add(processVO);
        });
        //同一个流程发布多个租户后，流程名称会有重复
        List<ProcessVO> processVOs = new ArrayList<>();
        Map<String, Object> nameMap = new HashMap<>();
        for (ProcessVO processVO : processVOList) {
            if (!nameMap.containsKey(processVO.getName())) {
                nameMap.put(processVO.getName(),processVO);
                processVOs.add(processVO);
            }
        }
        return processVOs;
	}

	/**
     * 获取进程 processDefinitionKey 的第一个节点定义
     *
     * @param processDefinitionKey
     * @return
     * @throws Exception
     */
	@Override
    public ActivityImpl getFirstNode(String processDefinitionKey) throws Exception {
        ActivityImpl activityImpl = null;
        ProcessDefinition processdefinition = getNewProcessDefinition(processDefinitionKey);
        if (processdefinition != null) {
            BpmnModel model = repositoryService.getBpmnModel(processdefinition.getId());
            ProcessDefinitionEntity processDefinitionEntity = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
                    .getDeployedProcessDefinition(processdefinition.getId());
            if (model != null) {
                // 获取所有节点
                Collection<FlowElement> flowElements = model.getMainProcess().getFlowElements();
                for (FlowElement e : flowElements) {
                    if (e instanceof StartEvent) {
                        StartEvent startEvent = (StartEvent) e;
                        ActivityImpl startEventActivityImpl = processDefinitionEntity.findActivity(startEvent.getId());// 当前节点
                        List<PvmTransition> outTransitions = startEventActivityImpl.getOutgoingTransitions();
                        for (PvmTransition tr : outTransitions) {
                            PvmActivity ac = tr.getDestination(); // 获取线路的终点节点
                            if ("userTask".equals(ac.getProperty("type"))) {
                                activityImpl = processDefinitionEntity.findActivity(ac.getId());// 当前节点
                                break;
                            } else if ("manualTask".equals(ac.getProperty("type"))) {
                                List<PvmTransition> outTransitionsManual = startEventActivityImpl.getOutgoingTransitions();
                                for (PvmTransition trManual : outTransitionsManual) {
                                    PvmActivity acManual = trManual.getDestination(); // 获取线路的终点节点
                                    if ("userTask".equals(acManual.getProperty("type"))) {
                                        activityImpl = processDefinitionEntity.findActivity(ac.getId());// 当前节点
                                        break;
                                    }
                                }
                            }
                        }
                        break;
                    }
                }
            }
        }
        return activityImpl;
    }

    /**
     * 根据key获得一个最新的流程定义
     *
     * @param processDefinitionKey
     * @return
     */
	private ProcessDefinition getNewProcessDefinition(String processDefinitionKey) {
		// 根据key查询已经激活的流程定义，并且按照版本进行降序。那么第一个就是将要得到的最新流程定义对象
        List<ProcessDefinition> processDefinitionList = repositoryService.createProcessDefinitionQuery()
                .processDefinitionId(processDefinitionKey).list();
        if (processDefinitionList.size() > 0) {
            return processDefinitionList.get(0);
        }
        return null;
	}
}
